export class Polygon {
	constructor(viewer, icesium) {
		this._viewer = viewer;
		this._iCesium = icesium;
		this._dataSource = new icesium.CustomDataSource('Polygon-data');
		this._viewer.dataSources.add(this._dataSource);
		this.decorates = new Map();
	}

	/**
	 * 添加标签
	 * @param polygonData {object}
	 * @param callback
	 * @returns {Promise<void>}
	 */
	async add(polygonData, callback) {
		const { id, coordinateType, fillColor, locationData, polygonType, ...other } = polygonData;
		if (this.decorates.get(id)) return console.error('decorator already registered');

		if (+coordinateType === 1) {
			const p = locationData.flat();
			if (Math.abs(p[0]) < Math.PI && Math.abs(p[1]) < Math.PI / 2) {
				this.hierarchy = this._iCesium.Cartesian3.fromRadiansArrayHeights([...p]);
			} else {
				this.hierarchy = this._iCesium.Cartesian3.fromDegreesArrayHeights([...p]);
			}
		} else {
			this.hierarchy = locationData.map((point) => this._iCesium.Cartesian3.fromArray(point));
		}

		if (+polygonType === 1) {
			const arr = [];
			this.hierarchy.forEach(({ x, y, z }) => arr.push([x, y, z]));
			const temp = new Test().filterPoint(arr);
			this.hierarchy = temp.map((item) => this._iCesium.Cartesian3.fromArray(item));
		}

		const color = (color) => color && this._iCesium.Color.fromCssColorString(color);

		this.material = color(fillColor);
		other.outlineColor = color(other.outlineColor || '#ffffff');
		other.perPositionHeight = other.perPositionHeight ?? true;

		this._callback = callback || undefined; // 回调函数
		this._userData = polygonData.userData; // any,用户自定义数据

		const param = Object.assign(
			{
				hierarchy: this.hierarchy,
				material: this.material
			},
			other
		);

		const polygon = this._dataSource.entities.add({
			id,
			coordinateType,
			userData: this._userData,
			polygon: param
		});

		let point;
		if (+polygonData.pointSize > 0) {
			point = this.hierarchy.map((point) => {
				return this._dataSource.entities.add({
					position: point,
					point: {
						show: true,
						disableDepthTestDistance: Number.POSITIVE_INFINITY,
						pixelSize: polygonData.pointSize,
						color: this._iCesium.Color.fromCssColorString(polygonData.pointColor || fillColor)
					}
				});
			});
		}

		this.decorates.set(id, { markerData: polygonData, polygon, point });

		callback && this.setHandler(callback);
	}

	setHandler(callback) {
		const viewer = this._viewer;
		if (!viewer.polygonHandler) {
			viewer.polygonHandler = new this._iCesium.ScreenSpaceEventHandler(viewer.scene.canvas);
		}
		const self = this;
		viewer.polygonHandler.setInputAction(function (click) {
			const pick = viewer.scene.pick(click.position);
			if (pick && typeof pick.id === 'object') {
				callback(self.decorates.get(pick.id.id.toString()));
			}
		}, this._iCesium.ScreenSpaceEventType.LEFT_CLICK);
	}

	/**
	 * 清空标签
	 * @returns {boolean}
	 */
	clear() {
		this._dataSource.entities.removeAll();
		this.decorates.clear();
		this._viewer.polygonHandler = null;
		return true;
	}

	/**
	 * 删除标签
	 * @returns {boolean}
	 */
	delete(id) {
		const marker = this.decorates.get(id);
		if (marker) {
			this._dataSource.entities.remove(marker.polygon);
			marker.point?.forEach((entity) => {
				this._dataSource.entities.remove(entity);
			});
			this.decorates.delete(id);
		}
		return true;
	}

	/**
	 * 获取标签
	 * @param {*} id
	 * @returns
	 */
	get(id) {
		return this._dataSource.entities.getById(id);
	}

	/**
	 * 更新标签
	 * @param id
	 * @returns {boolean}
	 */
	update(id, point, markerData) {}

	hide(ids) {}

	show(ids) {}

	hideAll() {}

	showAll() {}
}

export class Test {
	/**
	 * 过滤数据点
	 * @param locationData
	 * @returns {*[]}
	 */
	filterPoint(locationData = []) {
		let SMAndE = locationData.slice().splice(0, 3);
		const isCollinear = this.isCollinear(...SMAndE);

		// 如果前三点共线，去掉第二个点
		if (isCollinear) locationData.splice(1, 1);
		SMAndE = locationData.slice().splice(0, 3);

		// 计算平面法向量
		const vector = this.normalVector(...SMAndE);

		let projectionCoordinates = [];
		if (locationData.length > 3) {
			const locationDataCopy = locationData.slice();
			const SMAndE = locationDataCopy.splice(0, 3);

			// 计算投影坐标
			projectionCoordinates = locationDataCopy.map((point) => this.coordinate(vector, point));

			const isCrossArr = locationDataCopy.map((point, index) => {
				const points = SMAndE.concat(locationDataCopy.slice(0, index + 1));

				const resultL = [];
				const resultR = [];
				let isCross = false;
				for (let i = points.length - 3; i > 0; i--) {
					const res = this.getNode([
						points[points.length - 1],
						points[points.length - 2],
						points[i],
						points[i - 1]
					]);
					if (res) resultL.push(res);
				}
				for (let i = 1; i < points.length - 2; i++) {
					const res = this.getNode([
						points[points.length - 1],
						points[0],
						points[i],
						points[i + 1]
					]);
					if (res) resultR.push(res);
				}
				const lLength = resultL.length;
				const rLength = resultR.length;
				if (lLength === rLength && lLength === 1) {
					const distAB = this.getDistance(resultL[0], resultR[0]);
					isCross = distAB !== 0;
				}
				if (lLength !== rLength) isCross = true;

				if (isCross) locationDataCopy.splice(index, 1);

				return isCross;
			});

			// isCrossArr.forEach((isCross, index) => {
			//   isCross && projectionCoordinates.splice(index, 1)
			// })
		}

		return SMAndE.concat(projectionCoordinates);
	}

	/**
	 * 计算面积
	 * @param start
	 * @param mid
	 * @param end
	 * @returns {number}
	 */
	getArea(start, mid, end) {
		start = JSON.parse(JSON.stringify(start));
		mid = JSON.parse(JSON.stringify(mid));
		end = JSON.parse(JSON.stringify(end));

		const [sx, sy, sz] = start;
		const [mx, my, mz] = mid;
		const [ex, ey, ez] = end;

		let area = 0;
		// 海伦公式计算面积
		const disXY = Math.sqrt(Math.pow(mx - sx, 2) + Math.pow(my - sy, 2) + Math.pow(mz - sz, 2));
		const disYZ = Math.sqrt(Math.pow(ex - mx, 2) + Math.pow(ey - my, 2) + Math.pow(ez - mz, 2));
		const disXZ = Math.sqrt(Math.pow(ex - sx, 2) + Math.pow(ey - sy, 2) + Math.pow(ez - sz, 2));
		const p = (disXY + disYZ + disXZ) / 2;
		area += Math.sqrt(p * (p - disXY) * (p - disYZ) * (p - disXZ));

		return area;
	}

	/**
	 * 判断交点
	 * @param points
	 * @returns {boolean|[number, *, *]}
	 */
	getNode(points) {
		points = JSON.parse(JSON.stringify(points));
		const xArr = [];
		const yArr = [];
		const zArr = [];

		let indexA = 0;
		let indexB = 1;
		let indexC = 2; // 坐标为0 辅助确定平面
		points.forEach((item, index) => {
			if (item.length === 3) {
				xArr.push(parseFloat(item[0].toFixed(3)));
				yArr.push(parseFloat(item[1].toFixed(3)));
				zArr.push(parseFloat(item[2].toFixed(3)));
			}
		});
		const x = this.isSame(xArr);
		const y = this.isSame(yArr);
		const z = this.isSame(zArr);
		if (x && !y && !z) {
			indexA = 1;
			indexB = 2;
			indexC = 0;
		} else if (!x && y && !z) {
			indexA = 0;
			indexB = 2;
			indexC = 1;
		} else if (!x && !y && z) {
			indexA = 0;
			indexB = 1;
			indexC = 2;
		}
		const pointA = points[0];
		const pointB = points[1];
		const pointC = points[2];
		const pointD = points[3];

		const areaABC =
			(pointA[indexA] - pointC[indexA]) * (pointB[indexB] - pointC[indexB]) -
			(pointA[indexB] - pointC[indexB]) * (pointB[indexA] - pointC[indexA]);
		const areaABD =
			(pointA[indexA] - pointD[indexA]) * (pointB[indexB] - pointD[indexB]) -
			(pointA[indexB] - pointD[indexB]) * (pointB[indexA] - pointD[indexA]);

		if (areaABC * areaABD >= 0) {
			return false;
		}

		const areaCDA =
			(pointC[indexA] - pointA[indexA]) * (pointD[indexB] - pointA[indexB]) -
			(pointC[indexB] - pointA[indexB]) * (pointD[indexA] - pointA[indexA]);
		const areaCDB = areaCDA + areaABC - areaABD;

		if (areaCDA * areaCDB >= 0) {
			return false;
		}

		const t = areaCDA / (areaABD - areaABC);

		const a = t * (pointB[indexA] - pointA[indexA]) + pointA[indexA];
		const b = t * (pointB[indexB] - pointA[indexB]) + pointA[indexB];

		let point = [];
		switch (indexC) {
			case 0:
				point = [0, a, b];
				break;
			case 1:
				point = [a, 0, b];
				break;
			case 2:
				point = [a, b, 0];
				break;
			default:
				point = [a, b, 0];
				break;
		}
		return point;
	}

	/**
	 * 判断点在区域内
	 * @param point
	 * @param pointsArr
	 * @returns {boolean}
	 */
	rayCasting(point, pointsArr) {
		point = JSON.parse(JSON.stringify(point));
		pointsArr = JSON.parse(JSON.stringify(pointsArr));

		const xArr = [];
		const yArr = [];
		const zArr = [];

		let indexA = 0;
		let indexB = 1;
		pointsArr.forEach((item) => {
			if (item.length === 3) {
				xArr.push(parseFloat(item[0].toFixed(3)));
				yArr.push(parseFloat(item[1].toFixed(3)));
				zArr.push(parseFloat(item[2].toFixed(3)));
			}
		});
		const x = this.isSame(xArr);
		const y = this.isSame(yArr);
		const z = this.isSame(zArr);
		if (x && !y && !z) {
			indexA = 1;
			indexB = 2;
		} else if (!x && y && !z) {
			indexA = 0;
			indexB = 2;
		} else if (!x && !y && z) {
			indexA = 0;
			indexB = 1;
		}
		const px = point[indexA];
		const py = point[indexB];
		let flag = false;

		for (var i = 0, len = pointsArr.length - 1; i < len; i++) {
			const fx = pointsArr[i][indexA];
			const fy = pointsArr[i][indexB];
			const sx = pointsArr[i + 1][indexA];
			const sy = pointsArr[i + 1][indexB];
			// 点与多边形顶点重合
			if ((fx === px && fy === py) || (sx === px && sy === py)) {
				return true;
			}
			// 判断线段两端点是否在射线两侧
			if ((fy < py && sy >= py) || (fy >= py && sy < py)) {
				// 线段上与射线 Y 坐标相同的点的 X 坐标
				const x = fx + ((py - fy) * (sx - fx)) / (sy - fy);
				// 点在多边形的边上
				if (x === px) {
					return true;
				}
				// 射线穿过多边形的边界
				if (x > px) {
					flag = !flag;
				}
			}
		}
		// 射线穿过多边形边界的次数为奇数时点在多边形内
		return flag;
	}

	/**
	 * 判断是否为同一点
	 * @param arr
	 * @returns {boolean}
	 */
	isSame(arr) {
		return Math.max(...arr) === Math.min(...arr);
	}

	/**
	 * 计算平面法向量
	 * @param start
	 * @param mid
	 * @param end
	 * @returns {number[]}
	 */
	normalVector(start, mid, end) {
		start = JSON.parse(JSON.stringify(start));
		mid = JSON.parse(JSON.stringify(mid));
		end = JSON.parse(JSON.stringify(end));

		const [X1, Y1, Z1] = start;
		const [X2, Y2, Z2] = mid;
		const [X3, Y3, Z3] = end;

		return [
			Y2 * Z3 - Y2 * Z1 - Y1 * Z3 - Y3 * Z2 + Y1 * Z2 + Y3 * Z1,
			X3 * Z2 - X1 * Z2 - X3 * Z1 - X2 * Z3 + X2 * Z1 + X1 * Z3,
			X2 * Y3 - X2 * Y1 - X1 * Y3 - X3 * Y2 + X3 * Y1 + X1 * Y2,
			X1 * Y3 * Z2 + X2 * Y1 * Z3 + X3 * Y2 * Z1 - X1 * Y2 * Z3 - X3 * Y1 * Z2 - X2 * Y3 * Z1
		];
	}

	/**
	 * 计算两点间距离
	 * @param start
	 * @param end
	 * @returns {number}
	 */
	getDistance(start, end) {
		start = JSON.parse(JSON.stringify(start));
		end = JSON.parse(JSON.stringify(end));
		return Math.sqrt(
			Math.pow(end[0] - start[0], 2) +
				Math.pow(end[1] - start[1], 2) +
				Math.pow(end[2] - start[2], 2)
		);
	}

	/**
	 * 计算三点之间夹角
	 * @param start
	 * @param mid
	 * @param end
	 * @returns {number}
	 */
	getAngleValue(start, mid, end) {
		start = JSON.parse(JSON.stringify(start));
		mid = JSON.parse(JSON.stringify(mid));
		end = JSON.parse(JSON.stringify(end));

		const baX = start[0] - mid[0];
		const baY = start[1] - mid[1];
		const baZ = start[2] - mid[2];
		const bcX = end[0] - mid[0];
		const bcY = end[1] - mid[1];
		const bcZ = end[2] - mid[2];

		const BABC = baX * bcX + baY * bcY + baZ * bcZ;
		const distBA = Math.sqrt(Math.pow(baX, 2) + Math.pow(baY, 2) + Math.pow(baZ, 2));
		const distBC = Math.sqrt(Math.pow(bcX, 2) + Math.pow(bcY, 2) + Math.pow(bcZ, 2));
		const cosValue = BABC / (distBA * distBC);

		return (Math.acos(cosValue) * 180) / Math.PI;
	}

	/**
	 * 计算投影坐标
	 * @param normalVector
	 * @param point
	 * @returns {number[]}
	 */
	coordinate(normalVector, point) {
		const [a, b, c, d] = normalVector;

		const distA = Math.pow(a, 2);
		const distB = Math.pow(b, 2);
		const distC = Math.pow(c, 2);
		const distABC = distA + distB + distC;

		const [x, y, z] = point;

		return [
			((distB + distC) * x - a * (b * y + c * z + d)) / distABC,
			((distA + distC) * y - b * (a * x + c * z + d)) / distABC,
			((distA + distB) * z - c * (a * x + b * y + d)) / distABC
		];
	}

	/**
	 * 判断三点共面
	 * @param start
	 * @param mid
	 * @param end
	 * @returns {boolean}
	 */
	isCollinear(start, mid, end) {
		// 计算面积
		const area = this.getArea(start, mid, end).toFixed(3);
		// 计算任意两边之和与第三边
		const distAB = this.getDistance(start, mid);
		const distBC = this.getDistance(mid, end);
		const distAC = this.getDistance(start, end);
		const sumACBC = distAC + distBC;
		const differ = (sumACBC - distAB).toFixed(3);

		return area < 0.1 && differ < 0.1;
	}
}
